#include "actor.h"
#include "game.h"
#include "map.h"
#include "buff.h"
#include "buffs.h"
#include "bcreature.h"
#include "creature.h"
#include "random.h"
#include "player.h"

Actor::Actor()
: _map( 0 )
, _x( 0 )
, _y( 0 )
, _destroy( false )
{
}

Actor::~Actor()
{
    InvalidateReferences();
}

void Actor::SetMap( Map* map, uint x, uint y )
{
    if ( IsDestroyed() )
        throw std::runtime_error( "Setting map of a destroyed actor" );

    InvalidateReferences();

    int time;
    if ( _map ) {
        _map->GetTile( _x, _y ).Remove( this );
        time = _map->Remove( this );
    }
    else {
        int t = 0;
        Call< BISpeed >( &t );
        time = t;
    }

    _map = map;

    if ( _map ) {
        if ( x < 0 || x >= _map->GetWidth() || y < 0 || y >= _map->GetHeight() )
            throw std::runtime_error( "Actor moved outside map bounds" );
        _x = x;
        _y = y;
        _map->GetTile( _x, _y ).Add( this );
        _map->Add( this, time );
    }
}

void Actor::SetPosition( uint x, uint y )
{
    if ( IsDestroyed() )
        throw std::runtime_error( "Setting position of a destroyed actor" );

    if ( _map ) {
        _map->GetTile( _x, _y ).Remove( this );
    }
    else
        throw std::runtime_error( "Actor on null map" );

    if ( x < 0 || x >= _map->GetWidth() || y < 0 || y >= _map->GetHeight() )
        throw std::runtime_error( "Actor moved outside map bounds" );

    _x = x;
    _y = y;
    _map->GetTile( _x, _y ).Add( this );
}

const Map* Actor::GetMap() const
{
    return _map;
}

Map* Actor::GetMap()
{
    return _map;
}

int Actor::GetX( uint direction ) const
{
    int x = 0;
    if ( direction == 1 || direction == 4 || direction == 7 )
        x = -1;
    if ( direction == 3 || direction == 6 || direction == 9 )
        x = 1;
    return _x + x;
}

int Actor::GetY( uint direction ) const
{
    int y = 0;
    if ( direction == 1 || direction == 2 || direction == 3 )
        y = 1;
    if ( direction == 7 || direction == 8 || direction == 9 )
        y = -1;
    return _y + y;
}

void Actor::Destroy()
{
    if ( IsDestroyed() )
        throw std::runtime_error( "Destroying a destroyed actor" );

    if ( !_map )
        throw std::runtime_error( "Destroying an unattached actor" );
    BuffList t;
    GetAllBuffs( t );
    foreach ( Buff* b, t )
        b->Destroy();
    _map->GetGame().DestroyActor( this );
    InvalidateReferences();
    SetMap( 0, 0, 0 );
    _destroy = true;
}

bool Actor::IsDestroyed() const
{
    return _destroy;
}

bool Actor::PrioritySort( const Actor* less, const Actor* greater )
{
    int lp, gp;
    less->Call< BIPriority >( &lp );
    greater->Call< BIPriority >( &gp );
    if ( lp == gp )
        return less < greater;
    return lp > gp;
}

uint Actor::DirectionTo( int x, int y )
{
    if ( x < GetX() ) {
        if ( y < GetY() )
            return 7;
        if ( y == GetY() )
            return 4;
        return 1;
    }
    if ( x == GetX() ) {
        if ( y < GetY() )
            return 8;
        if ( y == GetY() )
            return 5;
        return 2;
    }
    if ( y < GetY() )
        return 9;
    if ( y == GetY() )
        return 6;
    return 3;
}

void Actor::GetAdjacentActors( ActorList& outputList, uint flammableMask )
{
    for ( uint d = 1; d <= 9; ++d ) {
        int x = GetX( d );
        int y = GetY( d );
        if ( GetMap()->IsWithinMap( x, y ) ) {
            Tile& t = GetMap()->GetTile( x, y );
            ActorList list;
            t.GetActorsOnTile( list );
            foreach ( Actor* a, list ) {
                uint flammableType;
                a->Call< BIFlammable >( &flammableType );
                if ( ( !flammableMask || flammableType & flammableMask ) && !a->IsDestroyed() )
                    outputList.push_back( a );
            }
        }
    }
}

void Actor::GetAdjacentActors( ConstActorList& outputList, uint flammableMask ) const
{
    for ( uint d = 1; d <= 9; ++d ) {
        int x = GetX( d );
        int y = GetY( d );
        if ( GetMap()->IsWithinMap( x, y ) ) {
            const Tile& t = GetMap()->GetTile( x, y );
            ConstActorList list;
            t.GetActorsOnTile( list );
            foreach ( const Actor* a, list ) {
                uint flammableType;
                a->Call< BIFlammable >( &flammableType );
                if ( ( !flammableMask || flammableType & flammableMask ) && !a->IsDestroyed() )
                    outputList.push_back( a );
            }
        }
    }
}

bool Actor::CanBeSeenByPlayer() const
{
    const Creature* p = GetMap()->GetGame().GetPlayer();
    return p->CanSeeTile( GetX(), GetY() ) && p->CanSeeActor( this );
}

bool Actor::IsActor( Actor** actor ) const
{
    *actor = const_cast< Actor* >( this );
    return true;
}

bool Actor::Symbol( IO::SymList* list ) const
{
    list->push_back( IO::Sym( '?', Colour( 255, 255, 255 ) ) );
    return true;
}

bool Actor::Speed( int* speed ) const
{
    *speed += 10;
    return true;
}

bool Actor::Name( std::string* name, bool* plural, uint article ) const
{
    if ( name->empty() && article == ARTICLE_INDEFINITE )
        *name += "an ";
    if ( article == ARTICLE_PLURAL )
        *name += "unknown objects";
    else
        *name += "unknown object";
    return true;
}


bool Actor::OnSetFire()
{
    uint flammable;
    Call< BIFlammable >( &flammable );

    if ( flammable & BIFlammable::FLAMMABLE_BURN_INDEFINITE ) {
        ( new OnFireBuff() )->SetTarget( this );
        return true;
    }
    if ( flammable & BIFlammable::FLAMMABLE_BURN_EXTRA_LONG ) {
        Buff* b = new OnFireBuff();
        b->StartTimer( Random::Int( EXTRA_LONG_DICE ) );
        b->SetTarget( this );
        return true;
    }
    if ( flammable & BIFlammable::FLAMMABLE_BURN_LONG ) {
        Buff* b = new OnFireBuff();
        b->StartTimer( Random::Int( LONG_DICE ) );
        b->SetTarget( this );
        return true;
    }
    if ( flammable & BIFlammable::FLAMMABLE_BURN_SHORT ) {
        Buff* b = new OnFireBuff();
        b->StartTimer( Random::Int( SHORT_DICE ) );
        b->SetTarget( this );
        return true;
    }
    if ( flammable & BIFlammable::FLAMMABLE_BURN_EXTRA_SHORT ) {
        Buff* b = new OnFireBuff();
        b->StartTimer( Random::Int( EXTRA_SHORT_DICE ) );
        b->SetTarget( this );
        return true;
    }

    if ( flammable & BIFlammable::FLAMMABLE_CHAR )
        ( new CharredBuff() )->SetTarget( this );

    if ( flammable & BIFlammable::FLAMMABLE_SMOULDER_LONG ) {
        Buff* b = new SmoulderingBuff();
        b->StartTimer( Random::Int( LONG_DICE ) );
        b->SetTarget( this );
        return true;
    }
    if ( flammable & BIFlammable::FLAMMABLE_SMOULDER_SHORT ) {
        Buff* b = new SmoulderingBuff();
        b->StartTimer( Random::Int( SHORT_DICE ) );
        b->SetTarget( this );
        return true;
    }

    if ( flammable & BIFlammable::FLAMMABLE_DESTROY ) {
        if ( !IsDestroyed() ) {
            Bank().Unary( UNARY_FIRE_DESTROY_INSTANT, this );
            Destroy();
        }
    }
    return true;
}

bool Actor::OnMove( uint* direction, bool* moved )
{
    int x = GetX( *direction );
    int y = GetY( *direction );

    if ( x == GetX() && y == GetY() ) {
        *moved = false;
        return false;
    }

    if ( !GetMap()->IsWithinMap( x, y ) ) {
        *moved = false;
        return false;
    }

    const Tile& t = GetMap()->GetTile( x, y );
    if ( t.IsBlocked() ) {
        *moved = false;
        return false;
    }

    SetPosition( x, y );
    *moved = true;
    return true;
}

bool Actor::OnPushed( Actor* source, bool reverse, bool* pushed, bool first )
{
    int ignore;
    int sourceStr, sourceDex, thisStr, thisDex;
    bool bignore;
    uint pushType;
    Call< BIBlocking >( &bignore, &bignore, &pushType );
    Call< BIStats >( &ignore, &ignore, &thisStr, &thisDex, &ignore, &ignore, &ignore, &ignore );
    bool stats = source->Call< BIStats >( &ignore, &ignore, &sourceStr, &sourceDex, &ignore, &ignore, &ignore, &ignore );
    bool success = !stats || ( pushType == BIBlocking::PUSH_DISPLACE && sourceDex + sourceStr >= thisDex + thisStr ) || ( pushType == BIBlocking::PUSH_MOVE && sourceStr >= thisStr );

    if ( pushType == BIBlocking::PUSH_NONE || !success ) {
        if ( first )
            Bank().Binary( reverse ? BINARY_DRAG_FAIL : BINARY_SHOVE_FAIL, source, this );

        *pushed = false;
        return true;
    }

    if ( reverse ) {
        if ( first )
            Bank().Binary( BINARY_DRAG_SUCCEED, source, this );

        uint d = DirectionTo( source->GetX(), source->GetY() );
        SetPosition( GetX( d ), GetY( d ) );
        *pushed = true;
        return true;
    }

    if ( pushType == BIBlocking::PUSH_DISPLACE ) {
        if ( source->IsPlayer() && first )
            Bank().Binary( BINARY_PUSH_SUCCEED, source, this );

        SetPosition( source->GetX(), source->GetY() );
        *pushed = true;
        return true;
    }
    
    uint d = source->DirectionTo( GetX(), GetY() );
    bool moved;
    std::string s = Bank().GetBinary( BINARY_SHOVE_SUCCEED, source, this );
    Call< BIOnMove >( &d, &moved );
    if ( moved ) {
        if ( source->IsPlayer() && first )
            QueueMessage( s );

        *pushed = true;
        return true;
    }

    // Blocked by something
    int x = GetX( d );
    int y = GetY( d );
    if ( !GetMap()->IsWithinMap( x, y ) || GetMap()->GetTile( x, y ).IsBlockedByTerrain() ) {
        if ( first )
            Bank().Binary( BINARY_SHOVE_FAIL, source, this );

        *pushed = false;
        return true;
    }

    // Blocked
    ActorList list;
    GetMap()->GetTile( x, y ).GetActorsOnTile( list );
    foreach ( Actor* a, list ) {
        bool b = false;
        bool ignore;
        uint uignore;
        a->Call< BIBlocking >( &b, &ignore, &uignore );
        if ( !b )
            continue;
        bool pushed;
        a->Call< BIOnPushed >( source, false, &pushed, false );
        if ( !pushed ) {
            if ( first )
                Bank().Binary( BINARY_SHOVE_FAIL, source, this );

            return true;
        }
    }
    
    Call< BIOnMove >( &d, &moved );
    s = Bank().GetBinary( BINARY_SHOVE_SUCCEED, source, this );
    if ( moved ) {
        if ( first )
            QueueMessage( s );

        *pushed = true;
        return true;
    }

    if ( first )
        Bank().Binary( BINARY_SHOVE_FAIL, source, this );

    *pushed = false;
    return true;
}

void Actor::InvalidateReferences()
{
    foreach ( bool* b, _refList )
        *b = true;
    _refList.clear();
}

ActorReference::ActorReference( Actor* a )
{
    Set( a );
}

ActorReference::ActorReference( const ActorReference& a )
{
    Set( a );
}

ActorReference::~ActorReference()
{
    Unset();
}

ActorReference& ActorReference::operator=( Actor* a )
{
    Unset();
    Set( a );
    return *this;
}

ActorReference& ActorReference::operator=( const ActorReference& a )
{
    if ( this == &a )
        return *this;

    Unset();
    Set( a );
    return *this;
}

bool ActorReference::operator==( const ActorReference& a ) const
{
    return _actor == a._actor;
}

bool ActorReference::operator!=( const ActorReference& a ) const
{
    return _actor != a._actor;
}

bool ActorReference::IsValid() const
{
    return !_invalidated && _actor;
}

ActorReference::operator bool() const
{
    return IsValid();
}

const Actor& ActorReference::operator*() const
{
    if ( !IsValid() )
        throw std::runtime_error( "Using invalidated actor reference" );

    return *_actor;
}

Actor& ActorReference::operator*()
{
    if ( !IsValid() )
        throw std::runtime_error( "Using invalidated actor reference" );

    return *_actor;
}

const Actor* ActorReference::operator->() const
{
    if ( !IsValid() )
        throw std::runtime_error( "Using invalidated actor reference" );

    return _actor;
}

Actor* ActorReference::operator->()
{
    if ( !IsValid() )
        throw std::runtime_error( "Using invalidated actor reference" );

    return _actor;
}

void ActorReference::Set( Actor* a )
{
    _actor           = a;
    _invalidated     = false;
    if ( !a )
        return;
    _actor->_refList.push_back( &_invalidated );
}

void ActorReference::Set( const ActorReference& a )
{
    _actor           = a._actor;
    _invalidated     = a._invalidated;
    if ( !_actor )
        return;
    _actor->_refList.push_back( &_invalidated );
}

void ActorReference::Unset()
{
    if ( !IsValid() )
        return;
    Actor::RefList::iterator i = _actor->_refList.begin();
    while ( *i != &_invalidated && i != _actor->_refList.end() )
        ++i;
    if ( i != _actor->_refList.end() )
        _actor->_refList.erase( i );
}
